#ifndef __APP_HPP__
#define __APP_HPP__

// #include <GL/glew.h>
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>

class App
{
private:
    const int SCR_WIDTH = 800;
    const int SCR_HEIGHT = 450;

public:
    static App *the_app;
    float aspect;
    glm::vec3 cameraPosition;
    glm::vec3 cameraFront;
    glm::vec3 cameraUp;
    float cameraSpeed;
    double timeLastFrame;
    double timeThisFrame;
    double timeAccumulate;
    int countFrames;
    bool showFps;
    bool firstMouse;
    double lastX;
    glm::vec3 lastCameraFront;
    bool captureCursor;

    App()
    {
        aspect = (float)SCR_WIDTH / (float)SCR_HEIGHT;
        cameraPosition = glm::vec3(0.0f, 0.0f, 10.0f);
        cameraFront = glm::vec3(0.0f, 0.0f, -1.0f);
        cameraUp = glm::vec3(0.0f, 1.0f, 0.0f);
        firstMouse = true;
    }

    static void onWindowSizeCallback(GLFWwindow */* window */, int width, int height)
    {
        glViewport(0, 0, width, height);
        the_app->aspect = (float)width / (float)height;
    }

    static void onKeyCallback(GLFWwindow *window, int key, int scancode, int action, int mods)
    {
        if (action == GLFW_PRESS)
            the_app->keyDown(window, key, scancode, mods);
    }

    virtual void processInput(GLFWwindow *window)
    {
        if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
        {
            cameraPosition += cameraSpeed * cameraFront;
        }
        if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
        {
            cameraPosition -= cameraSpeed * cameraFront;
        }
        if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
        {
            cameraPosition += cameraSpeed * glm::normalize(glm::cross(cameraFront, cameraUp));
        }
        if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
        {
            cameraPosition -= cameraSpeed * glm::normalize(glm::cross(cameraFront, cameraUp));
        }
    }

    static void onMouseMove(GLFWwindow */* window */, double xpos, double ypos)
    {
        if (!the_app->captureCursor)
        {
            return;
        }
        if (the_app->firstMouse)
        {
            the_app->lastX = xpos;
            the_app->lastCameraFront = the_app->cameraFront;
            the_app->firstMouse = false;
            return;
        }
        double xoffset = xpos - the_app->lastX;
        // the_app->lastX = xpos;

        double sensitivity = 0.001f; //灵敏度
        xoffset *= sensitivity;

        std::cout << "xpos:" << xpos << "   ypos:" << ypos ;
        std::cout << "    xoffset:" << xoffset << std::endl;
        glm::mat4 I(1.0f);
        glm::vec3 Y(0.0f, 1.0f, 0.0f);

        the_app->cameraFront = glm::vec3(glm::vec4(the_app->lastCameraFront, 1.0f) * glm::rotate(I, (float)xoffset, Y));
    }

    static void onMouseButton(GLFWwindow *window, int button, int action, int /* mods */)
    {
        if (action == GLFW_PRESS)
        {
            switch (button)
            {
            case GLFW_MOUSE_BUTTON_LEFT:
                glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
                the_app->captureCursor = true;
                return;
            }
            
        }
        else if (action == GLFW_RELEASE)
        {
            switch (button)
            {
            case GLFW_MOUSE_BUTTON_LEFT:
                if (the_app->captureCursor)
                {
                    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
                    the_app->captureCursor = false;
                }
                return;
            }
        }
    }

    virtual void init()
    {
    }

    virtual void display()
    {
    }

    virtual bool keyDown(GLFWwindow *window, int key, int scancode, int mods)
    {
        switch (key)
        {
            case GLFW_KEY_M: //切换线框模式和填充模式
            {
                static GLenum mode = GL_FILL;
                mode = (mode == GL_FILL ? GL_LINE : GL_FILL);
                glPolygonMode(GL_FRONT_AND_BACK, mode);
            } break;
            case GLFW_KEY_ESCAPE: //停止鼠标捕获，主要是应付鼠标被捕获的情况
            {
                if (the_app->captureCursor)
                {
                    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
                    the_app->captureCursor = false;
                }
            } break;
            case GLFW_KEY_F: //打开和关闭输出fps的功能，输出到控制台
            {
                the_app->showFps = (the_app->showFps == false ? true : false);
            } break;
            default:
                return false;
        }

        return true;
    }

    virtual void run(App *app)
    {
        if (the_app != NULL)
        { //同一时刻，只能有一个App运行
            std::cerr << "The the_app is already run." << std::endl;
            return;
        }
        the_app = app;

        glfwInit();
        GLFWwindow *window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "StudyOpenGL", NULL, NULL);
        if (window == NULL)
        {
            std::cerr << "Failed to create GLFW window" << std::endl;
            glfwTerminate();
            return;
        }
        glfwMakeContextCurrent(window);
        glfwSetWindowSizeCallback(window, onWindowSizeCallback);
        glfwSetKeyCallback(window, onKeyCallback);
        glfwSetCursorPosCallback(window, onMouseMove);
        glfwSetMouseButtonCallback(window, onMouseButton);
        // if (glewInit() != GLEW_OK)
        // {
        //     std::cerr << "Failed to initalize GLEW" << std::endl;
        //     return;
        // }
        
        // glad: load all OpenGL function pointers
        // ---------------------------------------
        if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
        {
            std::cout << "Failed to initialize GLAD" << std::endl;
            return;
        }


        init(); //Init主要是用来创建VAO、VBO等，并准备要各种数据
        countFrames = 0;

        while (!glfwWindowShouldClose(window))
        {
            display(); //这里才是渲染图形的主战场

            timeThisFrame = glfwGetTime();

            //记录帧渲染之后的时间，并计算帧率，如果输出帧率，则每一秒输出一次（主要是标准输出太慢）,同时计算cameraSpeed;
            double timeInterval = timeThisFrame - timeLastFrame;
            timeLastFrame = timeThisFrame;
            if (showFps)
            {
                if (timeAccumulate < 1.0)
                {
                    countFrames++;
                    timeAccumulate += timeInterval;
                }
                else
                {
                    std::cout << "  FPS: " << countFrames << "\r" << std::flush;
                    // std::cout.flush();
                    // std::cout << "FPS: " << countFrames << std::endl;
                    countFrames = 0;
                    timeAccumulate = 0;
                }
            }
            cameraSpeed = 2.5f * (float)timeInterval;

            glfwSwapBuffers(window);
            processInput(window);
            glfwPollEvents();
        }
        glfwDestroyWindow(window);

        glfwTerminate();
        return;
    }
};

App *App::the_app = NULL;

#define DECLARE_MAIN(a)                   \
    int main(int argc, const char **argv) \
    {                                     \
        a *app = new a;                   \
        app->run(app);                    \
        delete app;                       \
        return 0;                         \
    }

#endif
